/*
 * Copyright (C) 2010 Interactive Media Management
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package dk.i2m.converge.jsf.beans.administrator;

import dk.i2m.converge.core.content.NewsItemField;
import dk.i2m.converge.core.workflow.NewsItemFieldVisible;
import dk.i2m.converge.ejb.facades.WorkflowFacadeLocal;
import dk.i2m.converge.jsf.beans.BaseBean;
import dk.i2m.converge.core.workflow.Workflow;
import dk.i2m.converge.core.workflow.WorkflowState;
import dk.i2m.converge.core.workflow.WorkflowStatePermission;
import dk.i2m.converge.core.workflow.WorkflowStateType;
import dk.i2m.converge.core.workflow.WorkflowStep;
import dk.i2m.converge.core.workflow.WorkflowStepAction;
import dk.i2m.converge.core.workflow.WorkflowStepActionProperty;
import dk.i2m.converge.core.workflow.WorkflowStepValidation;
import dk.i2m.converge.ejb.services.DataNotFoundException;
import dk.i2m.jsf.JsfUtils;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.event.ActionEvent;
import javax.faces.model.DataModel;
import javax.faces.model.ListDataModel;

/**
 * Managed backing bean for <code>/administrator/Workflows.jspx</code>.
 *
 * @author Allan Lykke Christensen
 */
public class Workflows extends BaseBean {

    @EJB private WorkflowFacadeLocal workflowFacade;

    private DataModel workflows;

    private Workflow selected;

    private WorkflowState selectedWorkflowState = new WorkflowState();

    private String selectedTab = "tabWorkflow";

    private String selectedWorkflowStateTab = "tabWorkflowState";

    private String selectedWorkflowStepTab = "tabWorkflowStep";

    private String selectedWorkflowStepActionTab = "tabWorkflowStepAction";

    private WorkflowStateType selectedWorkflowStateType;

    private WorkflowStep selectedWorkflowStep;

    private WorkflowStepAction selectedWorkflowStepAction;

    private WorkflowStepActionProperty selectedWorkflowStepActionProperty = new WorkflowStepActionProperty();

    private WorkflowStepActionProperty deleteProperty;

    private List<NewsItemField> workflowStepRequiredFields;

    private List<NewsItemField> workflowStateVisibleFields;

    /**
     * Gets a {@link DataModel} containing the available workflows.
     *
     * @return {@link DataModel} containing the available workflows
     */
    public DataModel getWorkflows() {
        if (workflows == null) {
            workflows = new ListDataModel(workflowFacade.findAllWorkflows());
        }
        return workflows;
    }

    public Workflow getSelected() {
        return selected;
    }

    public void setSelected(Workflow selected) {
        this.selected = selected;
    }

    public WorkflowState getSelectedWorkflowState() {
        return selectedWorkflowState;
    }

    public void setSelectedWorkflowState(WorkflowState selectedWorkflowState) {
        this.selectedWorkflowState = selectedWorkflowState;
        setSelectedWorkflowStateType(selectedWorkflowState.getType());

        if (selectedWorkflowState.getVisibleFields() != null) {
            workflowStateVisibleFields = new ArrayList<NewsItemField>();
            for (NewsItemFieldVisible nifv : selectedWorkflowState.getVisibleFields()) {
                workflowStateVisibleFields.add(nifv.getField());
            }
        }

    }

    public WorkflowStateType getSelectedWorkflowStateType() {
        return selectedWorkflowStateType;
    }

    public void setSelectedWorkflowStateType(WorkflowStateType selectedWorkflowStateType) {
        this.selectedWorkflowStateType = selectedWorkflowStateType;
    }

    /**
     * Gets the name of the selected tab.
     *
     * @return Name of the selected tab
     */
    public String getSelectedTab() {
        return selectedTab;
    }

    /**
     * Sets the name of the tab to select.
     *
     * @param selectedTab
     *          Name of the tab to select
     */
    public void setSelectedTab(String selectedTab) {
        this.selectedTab = selectedTab;
    }

    public String getSelectedWorkflowStateTab() {
        return selectedWorkflowStateTab;
    }

    public void setSelectedWorkflowStateTab(String selectedWorkflowStateTab) {
        this.selectedWorkflowStateTab = selectedWorkflowStateTab;
    }

    public WorkflowStep getSelectedWorkflowStep() {
        return selectedWorkflowStep;
    }

    public void setSelectedWorkflowStep(WorkflowStep selectedWorkflowStep) {
        this.selectedWorkflowStep = selectedWorkflowStep;

        if (selectedWorkflowStep.getValidation() != null) {
            workflowStepRequiredFields = new ArrayList<NewsItemField>();
            for (WorkflowStepValidation validation : selectedWorkflowStep.getValidation()) {
                workflowStepRequiredFields.add(validation.getField());
            }
        }
    }

    public String getSelectedWorkflowStepTab() {
        return selectedWorkflowStepTab;
    }

    public void setSelectedWorkflowStepTab(String selectedWorkflowStepTab) {
        this.selectedWorkflowStepTab = selectedWorkflowStepTab;
    }

    /**
     * Determines if the {@link Workflow} is in <em>edit</em> or <em>add</em>
     * mode.
     *
     * @return <code>true</code> if the {@link Workflow} is in <em>edit</em>
     *         mode and <code>false</code> if in <em>add</em> mode
     */
    public boolean isEditMode() {
        if (selected == null || selected.getId() == null) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * Determines if the {@link Workflow} is in <em>add</em> mode.
     *
     * @return <code>true</code> if the {@link Workflow} is in <em>add</em> mode
     *         and <code>false</code> if in <em>edit</em> mode
     */
    public boolean isAddMode() {
        return !isEditMode();
    }

    /**
     * Determines if the {@link WorkflowState} is in <em>edit</em> or <em>add</em>
     * mode.
     *
     * @return <code>true</code> if the {@link WorkflowState} is in <em>edit</em>
     *         mode and <code>false</code> if in <em>add</em> mode
     */
    public boolean isWorkflowStateEditMode() {
        if (selectedWorkflowState == null || selectedWorkflowState.getId() == null) {
            return false;
        } else {
            return true;
        }

    }

    /**
     * Determines if the {@link WorkflowState} is in <em>add</em> mode.
     *
     * @return <code>true</code> if the {@link WorkflowState} is in <em>add</em> mode
     *         and <code>false</code> if in <em>edit</em> mode
     */
    public boolean isWorkflowStateAddMode() {
        return !isWorkflowStateEditMode();
    }

    /**
     * Event handler for saving updates to the {@link Workflow}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onApplyWorkflow(ActionEvent event) {
        if (isAddMode()) {
            selected = workflowFacade.createWorkflow(selected);
            JsfUtils.createMessage("frmWorkflowDetails", FacesMessage.SEVERITY_INFO, "WORKFLOW_CREATED");
        } else {
            workflowFacade.updateWorkflow(selected);
            JsfUtils.createMessage("frmWorkflowDetails", FacesMessage.SEVERITY_INFO, "WORKFLOW_UPDATED");
        }

        this.workflows = null;
    }

    /**
     * Event handler for saving a {@link Workflow}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onSaveWorkflow(ActionEvent event) {
        if (isAddMode()) {
            selected = workflowFacade.createWorkflow(selected);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_CREATED");
        } else {
            workflowFacade.updateWorkflow(selected);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_UPDATED");
        }

        this.workflows = null;
    }

    /**
     * Event handler for saving updates to the {@link Workflow}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onDeleteWorkflow(ActionEvent event) {
        if (!isAddMode()) {
            workflowFacade.deleteWorkflowById(selected.getId());
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_DELETED");
        }

        this.workflows = null;
    }

    /**
     * Event handler for creating a new {@link Workflow}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onNewWorkflow(ActionEvent event) {
        selected = new Workflow();
    }

    /**
     * Event handler for creating a new {@link WorkflowState}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onNewWorkflowState(ActionEvent event) {
        selectedWorkflowState = new WorkflowState();
        selectedWorkflowState.setWorkflow(selected);
        selectedWorkflowState.setPermission(WorkflowStatePermission.USER);
        selectedWorkflowStateType = WorkflowStateType.MIDDLE;
        workflowStateVisibleFields = new ArrayList<NewsItemField>();
    }

    /**
     * Event handler for creating a new {@link WorkflowStateOption}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onNewWorkflowStep(ActionEvent event) {
        selectedWorkflowStep = new WorkflowStep();
        selectedWorkflowStep.setFromState(selectedWorkflowState);
        workflowStepRequiredFields = new ArrayList<NewsItemField>();
    }

    public void onNewWorkflowStepAction(ActionEvent event) {
        selectedWorkflowStepAction = new WorkflowStepAction();
        selectedWorkflowStepAction.setWorkflowStep(selectedWorkflowStep);
        // A default action class is required to avoid NullPointerException from JSF
        selectedWorkflowStepAction.setActionClass(dk.i2m.converge.plugins.alertaction.AlertAction.class.getName());
    }

    /**
     * Event handler for saving updates to the {@link WorkflowState}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onSaveWorkflowState(ActionEvent event) {

        if (isWorkflowStateAddMode()) {
            for (NewsItemField nif : this.workflowStateVisibleFields) {
                NewsItemFieldVisible nifv = new NewsItemFieldVisible();
                nifv.setField(nif);
                nifv.setWorkflowState(selectedWorkflowState);
                selectedWorkflowState.getVisibleFields().add(nifv);
            }

            selectedWorkflowState = workflowFacade.createWorkflowState(selectedWorkflowState);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_STATE_CREATED");
        } else {
            selectedWorkflowState.getVisibleFields().clear();

            for (NewsItemField nif : this.workflowStateVisibleFields) {
                NewsItemFieldVisible nifv = new NewsItemFieldVisible();
                nifv.setField(nif);
                nifv.setWorkflowState(selectedWorkflowState);
                selectedWorkflowState.getVisibleFields().add(nifv);
            }

            selectedWorkflowState = workflowFacade.updateWorkflowState(selectedWorkflowState);

            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_STATE_UPDATED");
        }

        try {
            selected = workflowFacade.findWorkflowById(selected.getId());

            if (selected.getStartState() != null && selected.getStartState().equals(selectedWorkflowState)) {
                selected.setStartState(null);
            }

            if (selected.getEndState() != null && selected.getEndState().equals(selectedWorkflowState)) {
                selected.setEndState(null);
            }

            if (selected.getTrashState() != null && selected.getTrashState().equals(selectedWorkflowState)) {
                selected.setTrashState(null);
            }

            if (selectedWorkflowStateType.equals(WorkflowStateType.START)) {
                logger.log(Level.FINE, "Setting the start state of the workflow");
                selected.setStartState(selectedWorkflowState);
            } else if (selectedWorkflowStateType.equals(WorkflowStateType.END)) {
                logger.finest("Setting the end state of the workflow");
                selected.setEndState(selectedWorkflowState);
            } else if (selectedWorkflowStateType.equals(WorkflowStateType.TRASH)) {
                logger.finest("Setting the trash state of the workflow");
                selected.setTrashState(selectedWorkflowState);
            }
            workflowFacade.updateWorkflow(selected);
            selected = workflowFacade.findWorkflowById(selected.getId());
        } catch (DataNotFoundException ex) {
            logger.log(Level.WARNING, "Workflow does not exist", ex);
        }
    }

    public void onApplyWorkflowState(ActionEvent event) {
        onSaveWorkflowState(event);
    }

    public void onDeleteWorkflowState(ActionEvent event) {
        try {
            workflowFacade.deleteWorkflowStateById(selectedWorkflowState.getId());
            selected = workflowFacade.findWorkflowById(selected.getId());
        } catch (DataNotFoundException ex) {
            logger.log(Level.WARNING, "Workflow does not exist", ex);
        }
    }

    /**
     * Event handler for saving updates to the {@link WorkflowStep}.
     *
     * @param event
     *          Event that invoked the handler
     */
    public void onSaveWorkflowStep(ActionEvent event) {
        if (isWorkflowStepAddMode()) {
            for (NewsItemField nif : this.workflowStepRequiredFields) {
                WorkflowStepValidation validation = new WorkflowStepValidation();
                validation.setField(nif);
                validation.setWorkflowStep(selectedWorkflowStep);
                selectedWorkflowStep.getValidation().add(validation);
            }

            selectedWorkflowStep = workflowFacade.createWorkflowStep(selectedWorkflowStep);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_STEP_CREATED");
        } else {
            // Remove existing validation
            selectedWorkflowStep.getValidation().clear();

            // Add new validation
            for (NewsItemField nif : this.workflowStepRequiredFields) {
                WorkflowStepValidation validation = new WorkflowStepValidation();
                validation.setField(nif);
                validation.setWorkflowStep(selectedWorkflowStep);
                selectedWorkflowStep.getValidation().add(validation);
            }

            selectedWorkflowStep = workflowFacade.updateWorkflowStep(selectedWorkflowStep);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_STEP_UPDATED");
        }


        // Update the workflow state (as it will not have different steps)
        try {
            selectedWorkflowState = workflowFacade.findWorkflowStateById(selectedWorkflowState.getId());
        } catch (DataNotFoundException ex) {
            logger.log(Level.SEVERE, ex.getMessage(), ex);
        }
    }

    public void onApplyWorkflowStep(ActionEvent event) {
        onSaveWorkflowStep(event);
    }

    public void onDeleteWorkflowStep(ActionEvent event) {
        try {
            workflowFacade.deleteWorkflowStepById(selectedWorkflowStep.getId());
            selectedWorkflowState = workflowFacade.findWorkflowStateById(selectedWorkflowState.getId());
        } catch (DataNotFoundException ex) {
            logger.log(Level.WARNING, "Workflow does not exist", ex);
        }
    }

    /**
     * Gets a {@link Map} of {@link WorkflowState}s for the workflow.
     *
     * @return {@link Map} of {@link WorkflowState}s
     */
    public Map<String, WorkflowState> getWorkflowStates() {
        Map<String, WorkflowState> states = new LinkedHashMap<String, WorkflowState>();
        for (WorkflowState state : selectedWorkflowStep.getFromState().getWorkflow().getStates()) {
            if (state.isGroupPermission()) {
                states.put(state.getName() + " (group " + state.getActorRole().getName() + ")", state);
            } else {
                states.put(state.getName() + " (user " + state.getActorRole().getName() + ")", state);
            }
        }

        return states;
    }

    /**
     * Determines if the {@link WorkflowStep} is in <em>edit</em> or <em>add</em>
     * mode.
     *
     * @return <code>true</code> if the {@link WorkflowStep} is in <em>edit</em>
     *         mode and <code>false</code> if in <em>add</em> mode
     */
    public boolean isWorkflowStepEditMode() {
        if (selectedWorkflowStep == null || selectedWorkflowStep.getId() == null) {
            return false;
        } else {
            return true;
        }

    }

    /**
     * Determines if the {@link WorkflowStep} is in <em>add</em> mode.
     *
     * @return <code>true</code> if the {@link WorkflowStep} is in <em>add</em> mode
     *         and <code>false</code> if in <em>edit</em> mode
     */
    public boolean isWorkflowStepAddMode() {
        return !isWorkflowStepEditMode();
    }

    public List<NewsItemField> getWorkflowStepRequiredFields() {
        return workflowStepRequiredFields;
    }

    public void setWorkflowStepRequiredFields(List<NewsItemField> workflowStepRequiredFields) {
        this.workflowStepRequiredFields = workflowStepRequiredFields;
    }

    public List<NewsItemField> getWorkflowStateVisibleFields() {
        return workflowStateVisibleFields;
    }

    public void setWorkflowStateVisibleFields(List<NewsItemField> workflowStateVisibleFields) {
        this.workflowStateVisibleFields = workflowStateVisibleFields;
    }

    public WorkflowStepAction getSelectedWorkflowStepAction() {
        return selectedWorkflowStepAction;
    }

    public void setSelectedWorkflowStepAction(WorkflowStepAction selectedWorkflowStepAction) {
        this.selectedWorkflowStepAction = selectedWorkflowStepAction;
    }

    public String getSelectedWorkflowStepActionTab() {
        return selectedWorkflowStepActionTab;
    }

    public void setSelectedWorkflowStepActionTab(String selectedWorkflowStepActionTab) {
        this.selectedWorkflowStepActionTab = selectedWorkflowStepActionTab;
    }

    /**
     * Determines if the {@link WorkflowStepAction} is in <em>edit</em> or
     * <em>add</em> mode.
     *
     * @return <code>true</code> if the {@link WorkflowStepAction} is in <em>edit</em>
     *         mode and <code>false</code> if in <em>add</em> mode
     */
    public boolean isWorkflowStepActionEditMode() {
        if (selectedWorkflowStepAction == null || selectedWorkflowStepAction.getId() == null) {
            return false;
        } else {
            return true;
        }

    }

    public boolean isWorkflowStepActionAddMode() {
        return !isWorkflowStepActionEditMode();
    }

    public WorkflowStepActionProperty getDeleteProperty() {
        return deleteProperty;
    }

    public void setDeleteProperty(WorkflowStepActionProperty deleteProperty) {
        this.deleteProperty = deleteProperty;
        selectedWorkflowStepAction.getProperties().remove(deleteProperty);
    }

    public WorkflowStepActionProperty getSelectedWorkflowStepActionProperty() {
        return selectedWorkflowStepActionProperty;
    }

    public void setSelectedWorkflowStepActionProperty(WorkflowStepActionProperty selectedWorkflowStepActionProperty) {
        this.selectedWorkflowStepActionProperty = selectedWorkflowStepActionProperty;
    }

    public void onAddWorkflowStepActionProperty(ActionEvent event) {
        selectedWorkflowStepActionProperty.setWorkflowStepAction(selectedWorkflowStepAction);
        selectedWorkflowStepAction.getProperties().add(selectedWorkflowStepActionProperty);
        selectedWorkflowStepActionProperty = new WorkflowStepActionProperty();
    }

    public void onSaveWorkflowStepAction(ActionEvent event) {
        
        if (isWorkflowStepActionAddMode()) {
            selectedWorkflowStepAction.setWorkflowStep(selectedWorkflowStep);
            selectedWorkflowStepAction = workflowFacade.createWorkflowStepAction(selectedWorkflowStepAction);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_STEP_ACTION_CREATED");
        } else {
            selectedWorkflowStepAction = workflowFacade.updateWorkflowStepAction(selectedWorkflowStepAction);
            JsfUtils.createMessage("frmPage", FacesMessage.SEVERITY_INFO, "WORKFLOW_STEP_ACTION_UPDATED");
        }

        // Update the workflow state (as it will not have different steps)
        try {
            selectedWorkflowStep = workflowFacade.findWorkflowStepById(selectedWorkflowStep.getId());
        } catch (DataNotFoundException ex) {
            logger.log(Level.SEVERE, ex.getMessage(), ex);
        }
        
    }
}
